package com.qianfeng.smartdevice.service.impl;

//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼                  BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  
//  


import com.qianfeng.smartdevice.cache.DefaltRoleCache;
import com.qianfeng.smartdevice.cache.RoleCache;
import com.qianfeng.smartdevice.events.UserLoginFailEvent;
import com.qianfeng.smartdevice.events.UserLoginSuccessEvent;
import com.qianfeng.smartdevice.exceptions.MyBaseException;
import com.qianfeng.smartdevice.exceptions.ResultCode;
import com.qianfeng.smartdevice.mapper.DefaultRoleMapper;
import com.qianfeng.smartdevice.mapper.RoleMapper;
import com.qianfeng.smartdevice.mapper.UserMapper;
import com.qianfeng.smartdevice.mapper.UserRoleMapper;
import com.qianfeng.smartdevice.pojo.CheckType;
import com.qianfeng.smartdevice.pojo.DefaultRole;
import com.qianfeng.smartdevice.pojo.Role;
import com.qianfeng.smartdevice.pojo.User;
import com.qianfeng.smartdevice.service.UserService;
import com.qianfeng.smartdevice.utils.MD5Utils;
import com.qianfeng.smartdevice.utils.SecruitUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.Year;
import java.util.Date;
import java.util.List;

/**
 * Created by Jackiechan on 2021/12/29/21:06
 *
 * @author Jackiechan
 * @version 1.0
 * @since 1.0
 */
@Service
@Transactional
public class UserServiceImpl implements UserService {

    private UserMapper userMapper;

    private DefaltRoleCache defaltRoleCache;

    private RoleCache roleCache;

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    private UserRoleMapper userRoleMapper;


    private ApplicationContext context;

    @Autowired
    public void setUserRoleMapper(UserRoleMapper userRoleMapper) {
        this.userRoleMapper = userRoleMapper;
    }

    @Autowired
    public void setbCryptPasswordEncoder(BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    @Autowired
    public void setRoleCache(RoleCache roleCache) {
        this.roleCache = roleCache;
    }

    @Autowired
    public void setDefaltRoleCache(DefaltRoleCache defaltRoleCache) {
        this.defaltRoleCache = defaltRoleCache;
    }

    @Autowired
    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void addUser(User user) {
        //添加用户
        //用户如果传递的数据不完整怎么办
        //我们不能把错误交给数据库发现,因为这样子就请求数据库了,数据库的访问量会增加,数据库不太支持高并发
        //磁盘的io效率比较慢,即便是nvme协议的固态也无法承受非常高的访问量
        //这就是我们的已知异常,我们知道有的人就是会不传递我们必须要求传递的数据,所以我们就可以在代码中进行判断
        //如果不符合要求直接抛出异常
//
//        if (user.isEmpty(CheckType.ADD)) {
//
//        }
        Assert.isTrue(!user.isEmpty(CheckType.ADD), () -> {
            throw new MyBaseException("传递的数据不完整", ResultCode.DATA_NOT_ALLOWED);
        });
        //TODO  可以实现更大复杂的判断,比如邮箱是不是符合要求,手机号是不是符合要求,密码是不是符合要求,名字等等是不是符合要求
        //你传递的数据是完整的,但是数据如果已经存在了呢? 比如用户名或者邮箱或者手机号这种唯一性要求的存在了这么办
        //我们也考虑到了,可以先查询一下看看
        List<User> userList = userMapper.selectUserByUsernameOrPhoneOrEmail(user.getUsername(), user.getPhone(), user.getEmail());
        //如果想继续执行,则集合必须是空的

        Assert.isTrue(userList.size() == 0, () -> {
            throw new MyBaseException("数据已经存在", ResultCode.DATA_ALREADY_EXIST);
        });

        //可以添加,但是呢还有些东西需要做
        //密码现在是明文,我们需要进行处理
        //因为相同的内容总是得到相同的md5,所以我们需要加盐,来更改内容
        //还要避免出现一种情况, 比如用户传递的密码是123456 我们生成的盐是fasdfsgfsd 另外一个用户传递的密码是123456f 我们生成的盐是asdfsgfsd 最后看起来在库中是一样的,但是实际上不一样
        //为了减少这种情况,我们的盐不会只使用一次
        //1 先生成盐,盐要随机
      //  String pwdSalt = MD5Utils.getRandomString(15);
      //  user.setPwdSalt(pwdSalt);
      //  String md5 = MD5Utils.getMd5(user.getPassword(), pwdSalt, 1024);
        //我们现在使用的是spring security,密码有专门的方式,所以密码需要替换为security的方式
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));//设置md5密码
        user.setCreateTime(new Date());//设置注册日期
        user.setCreateBy(user.getUsername());//设/置创建者,这个其实主要针对的是后台管理员添加数据的时候知道是谁添加的,普通用户写用户自己就行
        // 用户的这个标识主要是帮忙快速分别是什么类型的人
        //这个数据来自于默认的用户格式,当然可以更改
        //首先查询默认的角色表,找到普通用户的默认角色
        //根据查询到的角色id去查询角色表,来获取用户的标识
        //  Long roleId = defaultRoleMapper.getRoleIdByType("user");
        DefaultRole defaultRole = defaltRoleCache.get("user");
//        String remark = roleMapper.selectRemarkByRoleId(roleId);
        Role role = roleCache.get(defaultRole.getRoleId());
        user.setRemark(role.getRemark());
        userMapper.addUser(user);
        //将用户的角色设置进去
        userRoleMapper.addUserRole(user.getUId(), role.getRoleId());
    }

    @Override
    public void login(String username, String password) {
        //用户传递的密码是一个明文密码,但是我们数据库中是一个MD5值
        //所以按照用户和密码的方式查询是不行的,必须按照用户名查询,
        //密码也没有办法在查询之前变成MD5 因为盐在库中
        //我们需要先根据用户名查询数据,查询到之后 拿到盐,再把用户传递的密码按照创建时候的规则再次生成MD5值
        //拿着新生成MD5值和数据库中查询到的MD5值进行比较
        //如果一致 则认为是正确的,不一致则认为是不正确的
        User user = userMapper.selectByUserName(username);
        //用户名可能不存在,我们已经考虑到了
        Assert.notNull(user, () -> {
            context.publishEvent(new UserLoginFailEvent(username));
            throw new MyBaseException("用户名或者密码错误", ResultCode.USERNAME_PASSWORD_ERROR);
        });
        String md5 = MD5Utils.getMd5(password, user.getPwdSalt(), 1024);//将用户传递的密码转成MD5

        Assert.isTrue(md5.equalsIgnoreCase(user.getPassword()), () -> {
            context.publishEvent(new UserLoginFailEvent(username));
            throw new MyBaseException("用户名或者密码错误", ResultCode.USERNAME_PASSWORD_ERROR);
        });
        System.err.println("用户名密码比对成功,登录成功");
        //保存到Session
        SecruitUtils.addUser2Session(user);
        //发送事件更新用户的登录状态,因为我在登录后可以不记录数据,也可以记录多个数据,还可以动态变化记录的数据
        //但是不管记录与否,记录多少都不影响登录本身的操作,所以这属于非核心的操作
        //非核心的操作就可以放到其他的类中进行执行,并且可以放到异步中操作
        context.publishEvent(new UserLoginSuccessEvent());
    }

    @Override
    public void updateLogin(String username, Date loginTime, String loginIp) {
        userMapper.updateLogin(username, loginTime, loginIp);

    }

    @Override
    public User selectByUserName(String userName) {
        return userMapper.selectByUserName(userName);
    }
}
